개요
쿠버네티스는 마이크로서비스 아키텍처를 운영할 때 필수적인 서비스 디스커버리 기능을 기본적으로 제공합니다!
여러 파드(Pod)가 동적으로 생성되고 소멸되더라도,
서비스(Service) 리소스를 통해 안정적인 네트워크 접근과 통신이 가능해집니다.
이번 글에서는 서비스 디스커버리의 기본 개념부터 시작해서, 실제 시나리오, 테스트 방법, 그리고 현업에서 마주칠 수 있는 골치 아픈 오류와 해결 방법까지 기록해보겠습니다!
🧐 서비스 디스커버리, 왜 필요할까요?
쿠버네티스 환경에서 파드(Pod)는 언제든지 생성되고, 삭제되고, 또 새로운 IP를 할당받으며 끊임없이 변합니다.
만약 프론트엔드 파드가 백엔드 파드와 통신해야 하는데, 백엔드 파드의 IP 주소를 하드코딩해놨다면 어떨까요?
백엔드 파드가 재시작되어 IP가 바뀌는 순간, 프론트엔드는 백엔드를 찾지 못하고 서비스는 장애로 이어질 것입니다. 😱
서비스 디스커버리는 이처럼 동적으로 변하는 환경에서 서비스(파드의 논리적인 그룹)들이 서로의 위치(IP 주소와 포트)를 자동으로 찾아낼 수 있게 해주는 매커니즘입니다.
덕분에 개발자는 개별 파드의 IP 주소에 신경 쓸 필요 없이, '서비스 이름'만으로 안정적으로 통신이 가능한거죠.
쿠버네티스는 크게 두 가지 방식으로 서비스 디스커버리를 구현합니다.
1. DNS 기반 서비스 디스커버리
클러스터 내부에 자체 DNS 서버(대부분 CoreDNS)를 운영하는 방식입니다.
새로운 서비스가 생성되면, 쿠버네티스는 서비스명.네임스페이스명.svc.cluster.local
과 같은 형태의 완전 정규 도메인 이름(FQDN)을 자동으로 생성해줍니다.
예를 들어, my-service
라는 서비스가 default
라는 네임스페이스에 있다면,
my-service.default.svc.cluster.local
로 접근할 수 있는거죠!
파드는 이 DNS 이름을 통해서 원하는 서비스의 IP(ClusterIP)를 얻어 통신합니다. 가장 보편적이고 권장되는 방식 입니다.
2. 환경 변수 기반 서비스 디스커버리
파드가 생성될 때, 해당 파드가 속한 네임스페이스에 이미 존재하는 모든 서비스의 정보를 환경 변수로 주입해줍니다.
예를 들어, redis-master
라는 서비스가 있다면
{서비스명}_SERVICE_HOST
와 {서비스명}_SERVICE_PORT
형태의 환경 변수(REDIS_MASTER_SERVICE_HOST
, REDIS_MASTER_SERVICE_PORT
)가 파드에 자동으로 설정됩니다.
하지만 이 방식은 파드보다 서비스가 먼저 생성되어 있어야만 환경 변수가 주입된다는 단점이 있습니다.
🎬 시나리오: 웹 애플리케이션 배포
개념만으로는 와닿지 않으시죠? 간단한 웹 애플리케이션 시나리오를 통해 서비스 디스커버리가 어떻게 작동하는지 살펴보겠습니다.
사용자의 요청을 받는 프론트엔드(frontend
) 와, 비즈니스 로직을 처리하고 데이터베이스와 통신하는 백엔드(backend-api
) 가 있다고 가정해봅시다.
1. backend-api
Deployment와 이를 외부에 노출할 backend-api-svc
라는 이름의 ClusterIP 타입 서비스를 생성합니다.
2. frontend
Deployment를 생성합니다. frontend
애플리케이션 코드에서는 백엔드 API를 호출할 때, IP 주소 대신 http://backend-api-svc:8080
와 같이 서비스의 DNS 이름을 사용합니다.
3. frontend
파드 내의 애플리케이션이 backend-api-svc
라는 도메인으로 HTTP 요청을 보냅니다.
4. 파드 내의 DNS 리졸버는 이 요청을 클러스터의 CoreDNS에 전달합니다.
5. CoreDNS는 backend-api-svc
에 해당하는 ClusterIP(예: 10.10.10.1)를 frontend
파드에 응답으로 보내줍니다.
6. frontend
파드는 응답받은 ClusterIP로 실제 요청을 보내고, kube-proxy
가 이 요청을 받아 backend-api-svc
에 연결된 실제 backend-api
파드 중 하나로 트래픽을 전달합니다.
이 과정에서 backend-api 파드의 IP가 바뀌거나, 파드 개수가 늘어나도 frontend
는 오직 backend-api-svc
라는 고정된 이름만 바라보기 때문에 아무런 영향을 받지 않고 안정적인 통신을 이어갈 수 있습니다.
✅ 서비스 디스커버리 테스트하기
"정말 잘 동작하고 있을까" 궁금하다면 직접 해봐야겠죠?
간단한 방법은 테스트용 파드를 하나 띄워서, 그 안에서 다른 서비스와 통신을 시도하는 것입니다.
1. 테스트용 파드 실행
디버깅에 유용한 busybox
이미지를 사용해 임시 파드를 실행하고 셸에 접속합니다.
kubectl run -it --rm --image=busybox:1.28 test-pod --restart=Never -- sh
2. DNS 이름으로 통신 확인 (nslookup
, curl
)
위에서 만든 backend-api-svc
서비스가 정상적으로 등록되었는지 nslookup
명령어로 확인합니다.
# test-pod 셸 내부에서 실행
# nslookup <서비스명>
nslookup backend-api-svc
# 정상 응답 예시
# Server: 10.96.0.10 (CoreDNS의 IP)
# Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
#
# Name: backend-api-svc.default.svc.cluster.local
# Address 1: 10.10.10.1 (backend-api-svc의 ClusterIP)
DNS 조회가 성공했다면, curl
이나 wget
으로 실제 통신을 테스트합니다.
# curl <서비스명>:<포트>
curl http://backend-api-svc:8080
3. 환경 변수 확인
환경 변수 방식이 잘 동작하는지 확인하려면 env
명령어로 주입된 변수 목록을 보면 됩니다.
# test-pod 셸 내부에서 실행
env | grep BACKEND_API_SVC
# 정상 출력 예시
# BACKEND_API_SVC_SERVICE_HOST=10.10.10.1
# BACKEND_API_SVC_SERVICE_PORT=8080
# BACKEND_API_SVC_PORT=tcp://10.10.10.1:8080
# ...